module Data

import lang::java::jdt::JavaADT;
import lang::java::jdt::Java;
import lang::java::jdt::JDT;
import IO;
import String;
import List;
import util::Math;
import Volume;
import UnitSize;
import Duplicate;
import ValueIO;
import UnitComplexity;

data RelEntry = root()
			  | package(str name)
			  | class(loc location, str name, Metrics metrics)
			  ;
			  
data Metrics = metrics(real LOC, real CC, real US, real DUP);	

map[str, Metrics] packageMetrics = ();		  
			  
rel[RelEntry,RelEntry] structure = {};


public rel[RelEntry,RelEntry] getStructureFile()
{
	loadStructure();
	return structure;
}

public map[str, Metrics] getPackageMetricsFile()
{
	loadStructure();
	return packageMetrics;
}

//When run for the first time this loads the structure and saves it to a file, takes a long time.
public void getAndSaveStructure()
{
	println("getting structure");
	structure = getStructure();
	structure = fillMetrics(structure);
	fillPackageMetrics();
	structure = remDoubles(structure);
	saveStructure();
}

public void saveStructure()
{
	writeTextValueFile(|file:///C:/structure.txt|,structure);
	writeTextValueFile(|file:///C:/packageMetrics.txt|,packageMetrics);
}

public void loadStructure()
{
	structure = readTextValueFile(#rel[RelEntry, RelEntry],|file:///C:/structure.txt|);	
	packageMetrics =  readTextValueFile(#map[str, Metrics],|file:///C:/packageMetrics.txt|);
}

//Gets the structure of the program and adds it to the global structure variable. 
public rel[RelEntry,RelEntry] getStructure()
{
	fm = extractProject(|project://SmallSql|);
	//fm = extractProject(|project://hsqldb|);
	BindingRel packages = fm@classes;
	iprintln("found bindingrel");
	for(<loc l,Entity e> <- packages)
	{
		structure += <root(),package(readable(e.id[0]))>;
		for(int i <- [0..size(e.id) -2])
		{			
			switch(e.id[i+1])
			{
				case package(_) : 
				{
				 	temp = <package(readable(e.id[i])),package(readable(e.id[i+1]))>; 
				 	structure += temp; 
				 	//Add packages to package Metrics map
				 	packageMetrics += (temp[0].name : metrics(toReal(0),toReal(0),toReal(0),toReal(0))); 
			 		packageMetrics += (temp[1].name : metrics(toReal(0),toReal(0),toReal(0),toReal(0)));
			 	} 
				case class(_) : { structure += <package(readable(e.id[i])),class(l,readable(e.id[i+1]), metrics(toReal(0),toReal(0),toReal(0),toReal(0)))>; break;}
			}	
		}
	}
	return structure;
}

public rel[RelEntry,RelEntry] fillMetrics(rel[RelEntry,RelEntry] input)
{
	//Calculate metrics for classes
	output =  visit(input)
	{	
		case <_,package(_)> : println("pack");
		case <pre,class> => <pre,fillEntryMetrics(class)>
	}
	return output;
}
//Remove extra stuff because it bugs. (Index out of bounds?)
public loc getNormLoc(loc input)
{
	loc normLoc = |temp:///|;
	normLoc.uri = input.uri;
	return normLoc;
}

//Fill the metrics of a class entry. 
public RelEntry fillEntryMetrics(RelEntry input)
{
	loc normLoc = getNormLoc(input.location);
	
	real amountLines = toReal(countLinesFile(normLoc));
	real complexity = getUnitComplexityClass([input.location]);
	real unitSize = unitSizeClass([normLoc]);
	real duplicate = toReal(getDuplicates(normLoc));
	
	println("amount lines :  <amountLines>  loc :  <input.location> CC: <complexity>  US : <unitSize> DUP : <duplicate>");
	return class(input.location, input.name, metrics(amountLines,complexity,unitSize,duplicate));
}

//Structure has to be filled before this can be calculated;
//Package metrics calculation takes some time but only needs to be done once and is then written to file. 
public void fillPackageMetrics()
{
	//GetStructure and package from files to save time.
	getPackageMetricsFile();
	getStructureFile();
	
	for(pack <- packageMetrics)
	{
		RelEntry relPack = package(pack);
		children = getChildren(relPack);		
		list[loc] locs = [ getNormLoc(c.location) | c <- children];
		packageMetrics[pack] = metrics(toReal(countLinesList(locs)),getUnitComplexityClass(locs),unitSizeClass(locs),toReal(getDuplicatesList(locs)));
	}
	saveStructure();
}

// Gets all the classes in a package
public list[RelEntry] getChildren(RelEntry input) 
{
	list[RelEntry] result = [];
	for(RelEntry entry <- structure[input])
	{
		switch(entry)
		{
			case package(_) : result += getChildren(entry); 
			case class(_,_,_) : result += [entry];
		}
	}
	return result;
}
//Function to remove doubles from tree, these are due to inner classes. 
public rel[RelEntry,RelEntry] remDoubles(rel[RelEntry,RelEntry] input)
{
	rel[RelEntry,RelEntry] returnVal = {};
	
	for(entry <- input)
	{
		switch(entry[1])
		{	
			case class(_,_,_) : {	if(contains(entry[1], returnVal)){println("Double! <entry[1]>");}
									else{returnVal += entry;} }
			default : returnVal += entry;
		}
	}
	return returnVal;
}

public bool contains(RelEntry toCheck, rel[RelEntry,RelEntry] input)
{
	int count = 0; 
	for(entry <- input)
	{
		if(class(_,_,_) := entry[1])
		{
			if(getNormLoc(entry[1].location) == getNormLoc(toCheck.location)){count +=1;}
		}
	}
	return count >= 1;
}
